﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using EuroManager.Domain.Simulator.Engine;
using EuroManager.Domain.Simulator.Helpers;

namespace EuroManager.Domain.Simulator.World
{
    public class Ball : MovingEntity, IBall
    {
        private static readonly float friction = 5;
        private static readonly float maxSpeed = 50;
        private static readonly float predictedLocationsTime = 5;
        private static readonly float predictedLocationsTimeStep = 0.25f;

        private IPitch pitch;
        private IPlayer owner = null;
        private List<TimeLocation> futureLocations = new List<TimeLocation>();

        public Ball(IPitch pitch, Vector2D startLocation)
            : base(startLocation, 0, friction, maxSpeed)
        {
            Contract.Requires(pitch != null);

            this.pitch = pitch;

            CalculateFutureLocations();
        }

        public IPlayer Owner
        {
            get { return owner; }
        }

        public bool HasOwner
        {
            get { return owner != null; }
        }

        public ITeam OwningTeam
        {
            get { return owner == null ? null : owner.Team; }
        }

        public IEnumerable<TimeLocation> FutureLocations
        {
            get { return futureLocations; }
        }

        public override void Update(float elapsedTime)
        {
            base.Update(elapsedTime);

            CheckCollisions();
            CalculateFutureLocations();
        }

        public void Trap(IPlayer player)
        {
            owner = player;
            Halt();
        }

        public void Free()
        {
            owner = null;
        }

        public void Kick(Vector2D velocity)
        {
            Hit(velocity);
        }

        public void KickAt(Vector2D target, float speedAtTarget)
        {
            float startSpeed = Physics.StartSpeed(speedAtTarget, DistanceTo(target), friction);
            Kick(DirectionTo(target) * startSpeed);
        }

        private void CalculateFutureLocations()
        {
            futureLocations.Clear();
            
            float timeToStop = TimeToStop();
            float time = predictedLocationsTimeStep;
            
            while (time <= predictedLocationsTime && time < timeToStop)
            {
                futureLocations.Add(new TimeLocation(time, FutureLocation(time)));
                time += predictedLocationsTimeStep;
            }

            futureLocations.Add(new TimeLocation(timeToStop, FutureLocation(timeToStop)));
        }

        private float TimeToStop()
        {
            return Physics.Time(Speed, 0, -friction);
        }

        private Vector2D FutureLocation(float time)
        {
            return Physics.Location(Location, Velocity, -friction, time);
        }

        private void CheckCollisions()
        {
            if (X <= 0 || pitch.Width <= X)
            {
                BounceX();
            }

            if (Y <= 0 || pitch.Height <= Y)
            {
                BounceY();
            }
        }

        private void BounceX()
        {
            Hit(new Vector2D(-Velocity.X, Velocity.Y));
        }

        private void BounceY()
        {
            Hit(new Vector2D(Velocity.X, -Velocity.Y));
        }
    }
}
